#include <windows.h>
#include <iostream>
#include <ctime>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include "gl\gl.h"
#include "gl\glu.h"
#include "gl\glaux.h"
#include "gl\glut.h"
#include "tgaLoader.h"
#include "player.h"
#include "imageLoader.h"
#include "models\md2model.h"
#include "constants.h"
#include "math.h"
#include "handyFunctions.h"
#include "gameParameters.h"
#include "gameMath.h"
#include "objLoader.h"
#include "skybox.h"
#include "hud.h"
#include "frustum.h"
#include "octree.h"
#include "weapons/weapons.h"
#include "opponents/wolf.h"
#include "opponents/centaur.h"
#include "opponents/soldier.h"
#include "opponents/opponents.h"
#include "item.h"
#include "BSPModel.h"
#include "error.h"

using namespace std;

//which keys did the user press
enum Keys {GO_UP, GO_DOWN, GO_LEFT, GO_RIGHT};

//defines player movements in all directions
short frontBack;
short leftRight;
short upDown;

bool keyboard[4];
bool leftButtonDown;
Player *player;
BSPModel level;

PFNGLACTIVETEXTUREARBPROC		glActiveTextureARB		 = NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = NULL;

float gamma = 10;

Frustum frustum;
int visibleFaces;

//choosing resolution
void setResolution()
{
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

	int mode;

	printf("Choose preferred resolution and press Enter:\n");
	printf(" 1 - 800x600\n 2 - 1024x768\n 3 - 1280x800\n");
	do
	{
		scanf_s("%d", &mode);
	} while(mode < 1 || mode > 3);

	switch(mode){
		case 1:
			GameParameters::screenWidth = 800;
			GameParameters::screenHeight = 600;
			glutGameModeString("800x600:32@60");
			break;

		case 2:
			GameParameters::screenWidth = 1024;
			GameParameters::screenHeight = 768;
			glutGameModeString("1024x768:32@60");
			break;

		case 3:
			GameParameters::screenWidth = 1280;
			GameParameters::screenHeight = 800;
			glutGameModeString("1280x800:32@60");
			break;
		default:
			GameParameters::screenWidth = 1280;
			GameParameters::screenHeight = 800;
			glutGameModeString("1280x800:32@60");
			break;
	}
	glutEnterGameMode();
}

//initialize OpenGL parameters and reset variables
void initializeOpenGL()
{
	glViewport(0, 0, GameParameters::screenWidth, GameParameters::screenHeight);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(70.0f,(GLfloat)GameParameters::screenWidth/(GLfloat)GameParameters::screenHeight, 10.0f , 4000.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	ShowCursor(false);
	SetCursorPos(0, 0);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);
	glCullFace(GL_FRONT);
	glEnable(GL_CULL_FACE);
	glClearColor(0.0, 0.0, 0.0, 1.0);
}
//initialize game parameters
void initialize()
{
	strcpy_s(GameParameters::opponentsPath, 128, "models/opponents/");
	strcpy_s(GameParameters::weaponsPath, 128, "models/weapons/");
	const char *fileName = "maps/Level.bsp";

	frontBack = 0;
	leftRight = 0;
	upDown = 0;

	glActiveTextureARB		 = (PFNGLACTIVETEXTUREARBPROC)	 wglGetProcAddress("glActiveTextureARB");
    glClientActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)   wglGetProcAddress("glClientActiveTextureARB");

	if(!glActiveTextureARB || !glClientActiveTextureARB)
	{
		// TODO: add an error message (the video card doesn't support multitexturing)
	}

	leftButtonDown = false;

	bool result = level.loadBSP(fileName);
	if (result == false)
	{
		Error::printError(MODEL_NOT_FOUND, fileName);
		return;
	}
	
	player = Player::createNewPlayer();
}


//creating the objects
void loadWorld()
{
	//int counter = 0;
	//int normalsCounter = 0;
	//for(int o = 0; o < g_3DModel.numOfObjects; o++)
	//	numberOfModelVertices += g_3DModel.object[o].numberOfFaces;

	//numberOfModelVertices *= 3;
	//modelVertices = new Vector[numberOfModelVertices+24];
	//modelNormals = new Vector[numberOfModelVertices];

	//for(int o = 0; o < g_3DModel.numOfObjects; o++)
	//{
	//	for (int i = 0; i < g_3DModel.object[o].numberOfFaces; ++i)
	//	{
	//		for (int j = 0; j < 3; ++j)
	//		{
	//			int vertIndex = g_3DModel.object[o].faces[i].vertIndex[j];
	//			modelVertices[counter++] = Vector(g_3DModel.object[o].vertices[ vertIndex ].x, g_3DModel.object[o].vertices[ vertIndex ].y, g_3DModel.object[o].vertices[ vertIndex ].z);
	//			modelNormals[normalsCounter++] = Vector(g_3DModel.object[o].pNormals[vertIndex].x, g_3DModel.object[o].pNormals[vertIndex].y, g_3DModel.object[o].pNormals[vertIndex].z);
	//		}
	//	}
	//}
	//
	////g_Octree.GetSceneDimensions(modelVertices, numberOfModelVertices);
	////g_Octree.CreateNode(modelVertices, modelNormals, numberOfModelVertices, g_Octree.GetCenter(), g_Octree.GetWidth());

	////Opponents::loadOpponents();

	//if (player != NULL)
	//{
	//	player->setTrianglesToCollide(modelVertices);
	//	player->setNumberOfTrianglesToCollide(numberOfModelVertices+24);
	//}
}

//displaying current frame
void displayFrame()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	//draw the map
	player->move(frontBack, leftRight, upDown);
	frustum.calculateFrustum();
	level.renderLevel(player->getPosition());
	//g_3DModel.draw(player->getPosition()[0], player->getPosition()[1], player->getPosition()[2]);

	//draw weapon
	//weapon should not permeate through walls
	//TODO: fix this! Disabling depth testing is not a good idea...
	//glDisable(GL_DEPTH_TEST);
	//if (player != NULL)
	//{
	//	if (player->getWeapon() != NULL)
	//		player->getWeapon()->draw();
	//}
		//glEnable(GL_DEPTH_TEST);
	
	//if (skybox != NULL)
	//	skybox->draw(player->position[0], player->position[1], player->position[2], 100, 100, 100);

	//hud->draw(player->hitPoints, player->getWeapon()->getAmmo(), player->frags, player->deaths, alphaBlending, items[1]->duration - difftime(now, player->tpdGained), items[0]->duration - difftime(now, player->tfmGained) );

	glutSwapBuffers();
}
//listing all objects selected by the viewfinder
GLuint getTheMostNearestHit(GLint hits, GLuint buffer[])
 {
 	//For each hit in the buffer are allocated 4 bytes:
 	//1. Number of hits selected (always one,
 	//							beacuse when we draw each object
 	//							we use glLoadName, so we replace the
 	//							prevous name in the stack)
 	//2. Min Z
 	//3. Max Z
 	//4. Name of the hit (glLoadName)

	 GLuint names, *ptr, minZ, *ptrNames = NULL;
	 ptr = (GLuint*)buffer;
	 minZ = 0xffffffff;

	 for (int i = 0; i < hits; i++)
	 {
		names = *ptr;		
		if (names == 0)
			continue;

		ptr++;
		if (*ptr < minZ)
		{
			minZ = *ptr;
			ptrNames = ptr+2;
		}
	  
		ptr += names+2;
	 }	

	 if (ptrNames == NULL)
		 return 0;

	 return *ptrNames;
 }

//selecting objects with the viewfinder
GLuint selectObjects(int x, int y)
{
 	GLuint buff[512] = {0};
	GLint hits, view[4] = {0};

 	glSelectBuffer(64, buff);
	glRenderMode(GL_SELECT);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
 		glLoadIdentity();
		glGetIntegerv(GL_VIEWPORT, view);
 		gluPickMatrix(x, y, 1.0, 1.0, view);
		gluPerspective(70.0f,(GLfloat)640/(GLfloat)480, 10.0f , 4000.0f);
 		glMatrixMode(GL_MODELVIEW);

		//TODO: screen shakes when the player moves and we call displayFrame
		displayFrame();

 		glMatrixMode(GL_PROJECTION);
 	glPopMatrix();
 	glMatrixMode(GL_MODELVIEW);
	glFlush();

	hits = glRenderMode(GL_RENDER);
	return getTheMostNearestHit(hits, buff);
 }

//do something with the objects that the player hit
void processSelectedObject(GLubyte objectName)
{
	//for (int i = 0; i < howManyOpponents; ++i)
	//{
	//	Opponent *opponent = Opponents::opponents[i];
	//	if (opponent != NULL)
	//	{
	//		if (objectName == opponent->getNumber())
	//			opponent->hit(player);
	//	}
	//}
}
//handling key pressures
void keyPressed(unsigned char c, int x, int y)
{
	if (c == 27)
	{
		glutLeaveGameMode();
		exit(0);
	}

	if (c == '1' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7' || c == '8' || c == '9')
	{
		player->changeWeapon(int(c) - 48);
	}

	if (c == 'w' || c == 'W')
		keyboard[GO_UP] = true;

	if (c == 's' || c == 'S')
		keyboard[GO_DOWN] = true;

	if (c == 'a' || c == 'A')
		keyboard[GO_LEFT] = true;

	if (c == 'd' || c == 'D')
		keyboard[GO_RIGHT] = true;

	if (c == 'c' || c == 'C')
		printf("x: %f, y: %f, z: %f\n", player->getPosition()[0], player->getPosition()[1], player->getPosition()[2]);
	
	if (c == ' ')
		player->jump();
}

//handling key releases
void keyReleased(unsigned char c, int x, int y)
{
	if (c == 'w' || c == 'W')
		keyboard[GO_UP] = false;

	if (c == 's' || c == 'S')
		keyboard[GO_DOWN] = false;

	if (c == 'a' || c == 'A')
		keyboard[GO_LEFT] = false;

	if (c == 'd' || c == 'D')
		keyboard[GO_RIGHT] = false;
}

//checks which keys are pressed
void checkKeyPressures()
{
	if (keyboard[GO_UP] && keyboard[GO_DOWN])
		frontBack = 0;
	else if (keyboard[GO_UP])
		frontBack = 1;
	else if (keyboard[GO_DOWN])
		frontBack = -1;
	else
		frontBack = 0;

	if (keyboard[GO_LEFT] && keyboard[GO_RIGHT])
		leftRight = 0;
	else if (keyboard[GO_RIGHT])
		leftRight = 1;
	else if (keyboard[GO_LEFT])
		leftRight = -1;
	else
		leftRight = 0;
}
//says what to do in every frame
void nextFrame(int value)
{
	checkKeyPressures();

	if (leftButtonDown)
	{
		//if (!player->isShooting() && player->getWeapon()->getAmmo() > 0)
		//{
		//	GLuint objectName = selectObjects(GameParameters::screenWidth/2, GameParameters::screenHeight/2);
		//	processSelectedObject(objectName);

		//	if(player != NULL)
		//		player->shoot();
		//}
	}

	//advance the animation
	//for (int i = 0; i < howManyOpponents; ++i)
	//{
	//	Opponent *opponent = Opponents::opponents[i];
	//	if (opponent != NULL)
	//	{
	//		opponent->advanceAnimation(player);
	//		opponent->checkDistanceToPlayer(player->getPosition());
	//	}
	//}

	glutTimerFunc(GameParameters::refreshRate, nextFrame, 1);
	glutPostRedisplay();
}


//handling the player motion (auxiliary function
//that is passed as a parameter to
//glutPassiveMotionFunc and glutMotionFunc functions)
void handleTurning(int x, int y)
{
	player->handleTurning(x, y);
}
//handling mouse button presses
void handleMouseButtons(int button, int state, int x, int y)
{
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
	{
		leftButtonDown = true;
	}
	else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
	{
		leftButtonDown = false;
	}
}

int main(int argc, char* argv[])
{
	glutInit(&argc, argv);
	setResolution();
	initializeOpenGL();
	initialize();
	glutTimerFunc(GameParameters::refreshRate, nextFrame,1);
	glutDisplayFunc(displayFrame);
	glutIgnoreKeyRepeat(1);
	glutKeyboardFunc(keyPressed);
	glutKeyboardUpFunc(keyReleased);
	glutPassiveMotionFunc(handleTurning);
	glutMotionFunc(handleTurning);
	glutMouseFunc(handleMouseButtons);

	glutMainLoop();
	getchar();
	return 0;
}
